package umage.action;

import java.awt.event.ActionEvent;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.util.HashMap;
import java.util.regex.MatchResult;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import javax.swing.filechooser.FileNameExtensionFilter;

import org.jhotdraw.draw.Drawing;
import org.jhotdraw.draw.Figure;

import umage.core.UMageApplication;
import umage.figures.ClassFigure;
import umage.figures.GeneralizationArrow;
import umage.figures.ImplementsArrow;
import umage.figures.InterfaceFigure;
import umage.projects.ClassDiagramProject;

@ActionInfo(name = "Export", menu = "Code")
public class ClassExportAction extends DefaultAction {
	protected static final Pattern fieldPattern = Pattern.compile(" *([+\\-#~/_]*) *(\\w+)[ :]*([\\w\\<\\> ]*?) *");
	protected static final Pattern methodPattern = Pattern.compile(" *([+\\-#~/_]*) *(\\w+) *\\( *(.*?) *\\)[ :]*([\\w\\<\\> ]*?) *");
	protected static final Pattern paraSplitPattern = Pattern.compile(" *, *");
	protected static final Pattern paraPattern = Pattern.compile(" *(\\w+)[ :]*([\\w\\<\\> ]*?) *");
	
	protected JFileChooser fileChooser;
	protected Drawing drawing;
	protected HashMap<String, ClassFigure> figures;
	
	public ClassExportAction() {
		fileChooser = new JFileChooser(System.getProperty("user.home"));
		fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
		fileChooser.setMultiSelectionEnabled(false);
		
		FileNameExtensionFilter zipFilter = new FileNameExtensionFilter("ZIP file", "zip");
		fileChooser.addChoosableFileFilter(zipFilter);
		fileChooser.setFileFilter(zipFilter);
	}
	
	@Override
	public void actionPerformed(ActionEvent e) {
		
		final UMageApplication app = getApplication();
		if (!(app.getCurrentProject() instanceof ClassDiagramProject)) return;
		
		if (fileChooser.showSaveDialog(null) != JFileChooser.APPROVE_OPTION) return;
		File zipFile = fileChooser.getSelectedFile();
		if (!zipFile.getPath().toLowerCase().endsWith(".zip")) {
			zipFile = new File(zipFile.getPath() + ".zip");
		}
		
		// Begin creating the map of all class figures
		drawing = ((ClassDiagramProject)app.getCurrentProject()).getEditor().getView().getDrawing();
		figures = new HashMap<String, ClassFigure>();
		for (Figure figure : drawing.getFigures()) {
			if (figure instanceof ClassFigure) {
				ClassFigure cFigure = (ClassFigure) figure;
				figures.put(cFigure.getName(), cFigure);
			}
		}
		
		FileOutputStream fos = null;
		ZipOutputStream zos = null;
		PrintStream ps = null;
		
		PrintStream defaultErr = System.err;
		ByteArrayOutputStream stringStream = new ByteArrayOutputStream();
		PrintStream errorIntercept = new PrintStream(stringStream);
		StringBuilder errorString = new StringBuilder();
		try {
			System.setErr(errorIntercept);
			fos = new FileOutputStream(zipFile);
			zos = new ZipOutputStream(fos);
			ps = new PrintStream(zos);
			
			for (ClassFigure figure : figures.values()) {
				stringStream.reset();
				
				// Ignore inner classes
				if (figure.getName().indexOf('.') != -1) continue;
				
				StringBuilder result = exportClass(figure, 0);
				if (result != null) {
					zos.putNextEntry(new ZipEntry(figure.getName() + ".java"));
					ps.print(result.toString());
					ps.flush();
					zos.closeEntry();
				}
				
				if (stringStream.size() > 0) {
					errorString.append(figure.getName());
					errorString.append('\n');
					errorString.append(stringStream.toString());
				}
			}	
			
		} catch (Exception ex) { ex.printStackTrace();
		} finally {
			System.setErr(defaultErr);
			try { if (ps != null)  ps.close(); } catch (Exception ex) {}
			try { if (zos != null) zos.close(); } catch (Exception ex) {}
			try { if (fos != null) fos.close(); } catch (Exception ex) {}
			ps = null;
			zos = null;
			fos = null;
		}
		
		figures = null;
		
		if (errorString.length() > 0) {
			JOptionPane.showMessageDialog(null, "Errors occured during exporting:\n" + errorString.toString(),
					"Export Error", JOptionPane.ERROR_MESSAGE);
		}
		
	}
	
	protected StringBuilder exportClass(ClassFigure figure, int tabStart) {
		StringBuilder result = new StringBuilder();
		addTabs(result, tabStart);
		result.append("public ");
		if (figure.isAbstract()) result.append("abstract ");
		if (figure instanceof InterfaceFigure) {
			result.append("interface ");
		} else {
			result.append("class ");
		}
			
		// Add name
		String simpleName = figure.getName();
		int startPoint = simpleName.lastIndexOf('.');
		if (startPoint != -1 && simpleName.length() > startPoint + 1) simpleName = simpleName.substring(startPoint + 1);
		if (simpleName.indexOf(' ') != -1) {
			System.err.println("Class name contains space: " + simpleName);
			return null;
		}
		result.append(simpleName);
		
		// Check for extend
		if (!(figure instanceof InterfaceFigure)) {
			for (Figure arrowFigure : drawing.getFigures()) {
				// See if the figure is a generalization arrow
				if (!(arrowFigure instanceof GeneralizationArrow)) continue;
				
				// See if the generalization arrow starts at the current figure
				GeneralizationArrow genArrow = (GeneralizationArrow) arrowFigure;
				if (genArrow.getStartFigure() != figure) continue;
				
				// See if the figure the generalization arrow connects to is a class figure
				Figure endFigure = genArrow.getEndFigure();
				if (endFigure == null || !(endFigure instanceof ClassFigure)) continue;
				
				// add the found extension to the output
				result.append(" extends ");
				result.append(((ClassFigure)endFigure).getName());
				
				break;
			}
		}
		
		// Check for interfaces
		boolean firstInterfaceFound = false;
		for (Figure arrowFigure : drawing.getFigures()) {
			// See if the figure is a generalization arrow
			if (!(arrowFigure instanceof ImplementsArrow)) continue;
			
			// See if the generalization arrow starts at the current figure
			ImplementsArrow impArrow = (ImplementsArrow) arrowFigure;
			if (impArrow.getStartFigure() != figure) continue;
			
			// See if the figure the generalization arrow connects to is a class figure
			Figure endFigure = impArrow.getEndFigure();
			if (endFigure == null || !(endFigure instanceof ClassFigure)) continue;
			
			// add the found extension to the output
			if (firstInterfaceFound) {
				result.append(", ");
			} else {
				if (figure instanceof InterfaceFigure) {
					result.append(" extends ");
				} else {
					result.append(" implements ");
				}
				firstInterfaceFound = true;
			}
			result.append(((ClassFigure)endFigure).getName());
		}
		
		result.append(" {\n\n");
		
		for (String rawField : figure.getFields()) {
			StringBuilder fieldResult = exportField(rawField, tabStart + 1);
			if (fieldResult != null) result.append(fieldResult);
		}
		
		result.append('\n');
		
		for (String rawMethod : figure.getMethods()) {
			StringBuilder methodResult = exportMethod(simpleName, figure.isAbstract(rawMethod), rawMethod, tabStart + 1);
			if (methodResult != null) result.append(methodResult);
		}
		
		for (ClassFigure innerFigure : figures.values()) {
			if (innerFigure.getName().startsWith(figure.getName() + '.')) {
				StringBuilder innerResult = exportClass(innerFigure, tabStart + 1);
				if (innerResult != null) result.append(innerResult);
			}
		}
		
		addTabs(result, tabStart);
		result.append("}\n\n");
		return result;
	}
	
	protected static StringBuilder exportField(String rawField, int tabStart) {
		StringBuilder result = new StringBuilder();
		Matcher m = fieldPattern.matcher(rawField);
		if (!m.matches()) {
			System.err.println("Unreadable Field: " + rawField);
			return result;
		}
		
		// Read the results of the matching sequence
		String rawMods = m.group(1);
		String name = m.group(2);
		String type = m.group(3);
		
		addTabs(result, tabStart);
		result.append(modsToString(rawMods));
		if (type.length() == 0) {
			result.append("Object");
		} else {
			result.append(type);
		}
		result.append(' ');
		result.append(name);
		result.append(";\n");
		
		return result;
	}
	
	protected static StringBuilder exportMethod(String className, boolean isAbstract, String rawMethod, int tabStart) {
		StringBuilder result = new StringBuilder();
		Matcher m = methodPattern.matcher(rawMethod);
		if (!m.matches()) {
			System.err.println("Unreadable Method: " + rawMethod);
			return result;
		}
		
		// Read the results of the matching sequence
		String rawMods = m.group(1);
		String name = m.group(2);
		String params = m.group(3);
		String type = m.group(4);
		
		
		// Further parse the raw parameters
		MatchResult[] paraData = null;
		if (params.length() > 0) {
			String[] paramParts = paraSplitPattern.split(params);
			if (paramParts.length > 0) {
				paraData = new MatchResult[paramParts.length];
				for (int i = 0; i < paramParts.length; i++) {
					String rawParam = paramParts[i];
					Matcher paramMatch = paraPattern.matcher(rawParam);
					if (paramMatch.matches()) {
						paraData[i] = paramMatch;
					} else {
						System.err.println("Unreadable Parameter in " + name + ": " + rawParam);
						paraData = null;
						break;
					}
				}
			}
		}
		
		addTabs(result, tabStart);
		result.append(modsToString(rawMods));
		if (isAbstract) result.append("abstract ");
		if (!className.equals(name)) {
			if (type.length() == 0) {
				result.append("void");
			} else {
				result.append(type);
			}
			result.append(' ');
		}
		result.append(name);
		result.append('(');
		
		// Insert parameters
		if (paraData != null) {
			for (int i = 0; i < paraData.length; i++) {
				MatchResult p = paraData[i];
				String pName = p.group(1);
				String pType = p.group(2);
				if (pType.length() == 0) pType = "int";
				result.append(pType);
				result.append(' ');
				result.append(pName);
				if (i + 1 != paraData.length) result.append(", ");
			}
		}
		
		result.append(") {\n");
		addTabs(result, tabStart + 1);
		result.append('\n');
		addTabs(result, tabStart);
		result.append("}\n\n");
		
		return result;
	}
	
	protected static String modsToString(String rawMods) {
		StringBuilder result = new StringBuilder();
		if (rawMods.indexOf('+') != -1) result.append("public ");
		if (rawMods.indexOf('-') != -1) result.append("private ");
		if (rawMods.indexOf('#') != -1) result.append("protected ");
		if (rawMods.indexOf('_') != -1) result.append("static ");
		return result.toString();
	}
	
	protected static void addTabs(StringBuilder b, int tabCount) {
		for (int i = 0; i < tabCount; i++) {
			b.append('\t');
		}
	}
	
}
